import { EventClass } from "@lib/alen-helper/node/baseClass";
import { RunTime } from "../runtime";
import { concatMap, from, last, map, mergeMap, of, Subscription, filter, first } from "rxjs";
import { AppAisle } from "@lib/alen-helper/node/appAisle";
import { fork } from "child_process";
import { LibUniqueID } from "@lib/alen-helper/node/uniqueID";
import * as Path from "path";

export namespace Application {
    export interface Options {
        script: string,
        cwd: string,
        cacheDir: string,
    }

    export interface Message {
        source: { appid: string, id: string | null },
        target: { appid: string, id: string | null },
        data: string,
    }

    export type CmdData = Runtime.Controller.Tray.CmdData & Runtime.Controller.Window.CmdData;
    export type CmdData_Client = Runtime.Controller.ComonnCmdData_Client & Runtime.Controller.Window.CmdData_Client & Runtime.Controller.Tray.CmdData_Client;
}
export class Application extends EventClass<{
    start: null,
    close: {
        preventClose: boolean,
    },
    destroy: null,
    build_window: string,
    destroy_window: string,
    destroy_all_window: null,
}> {
    constructor(
        options: Partial<Application.Options>,
        private runTime: RunTime,
        BaseOptions?: Partial<EventClass.BaseOptions>
    ) {
        super('Application', BaseOptions);
        this._options = Object.assign(this._options, options);
        if (this._options.cwd.length === 0) {
            let arr = this._options.script.replaceAll('\\', '/').split('/');
            let cwd = process.cwd();
            if (arr.length > 1) {
                this._options.script = arr.pop()!;
                this._options.cwd = Path.join(cwd, ...arr);
            } else {
                this._options.cwd = cwd;
            }
        }
        this.cacheDir = Path.join(this._options.cwd, this._options.cacheDir);
    }

    private _ipcChannel: string | null = null;
    get ipcChannel() {
        return this._ipcChannel;
    }

    public readonly cacheDir: string;
    private _appAisle: AppAisle<Application.CmdData_Client> | null = null;
    private _options: Application.Options = {
        script: '',
        cwd: '',
        cacheDir: '.alen-runtime',
    }

    get options() {
        return this._options;
    }

    private _subs: Subscription | null = null;
    private _win_hids: string[] = [];

    private _ready: boolean = false;
    get isReady() {
        return this._ready;
    }

    private _unSub() {
        if (this._subs) this._subs.unsubscribe();
        this._subs = null;
        this.runTime.operatorManager.removeEventHandlerById(...this._win_hids);
        this._win_hids = [];
    }

    private _sub() {
        this._unSub();
        if (!this._appAisle) return;

        this._subs = this.event('close')
            .pipe(concatMap(e => {
                if (e.preventClose) return of(null);
                this._ready = false;
                return this.destroy();
            }))
            .subscribe(() => {

            })

        this._subs.add(
            this._appAisle.event('close')
                .subscribe(data => {
                    this._ready = false;
                    this.triggerEvent('destroy', null);
                })
        );

        this._subs.add(
            this.event('destroy')
                .pipe(concatMap(() => this._destroy()))
                .subscribe(() => {
                    this._unSub();
                    this.destroyEvent();
                })
        );

        this._subs.add(
            this.runTime.windowController.event('build')
                .pipe(filter(e => e.item.appid === this.id))
                .subscribe(data => {
                    this.windowsIds.push(data.item.id);
                    this.triggerEvent('build_window', data.item.id);
                })
        );
        this._subs.add(
            this.runTime.windowController.event('destroy')
                .pipe(filter(e => e.item.appid === this.id))
                .subscribe(data => {
                    const i = this.windowsIds.indexOf(data.item.id);
                    if (i > -1) {
                        this.windowsIds.splice(i, 1);
                        this.triggerEvent('destroy_window', data.item.id);
                    }
                })
        );

        this._subs.add(
            this.event('destroy_window')
                .subscribe(() => {
                    if (this.windowsIds.length === 0) {
                        this.triggerEvent('destroy_all_window', null);
                    }
                })
        );
    }

    readonly windowsIds: string[] = [];

    private _buildId() {
        return LibUniqueID.id16();
    }

    private _runScript() {
        const script = this._options.script;
        const childProcess = fork(script, {
            cwd: this._options.cwd,
        });
        const id = this._buildId();
        this._ipcChannel = 'ipc@' + Path.join(this.cacheDir, 'ipc_' + id);
        this._log.log(`运行脚本 [id:${id}]: ${script}`);
        this.runTime.addMianProcess(id, childProcess);
        return this.runTime.event('mianAisle')
            .pipe(first(a => a.id === id))
            .pipe(concatMap((a: AppAisle<Application.CmdData_Client>) => {
                return a.cmd('app_activate_id', { appid: id, ipcChannel: this._ipcChannel!, cacheDir: this.cacheDir }).pipe(map(() => a));
            }))
            .pipe(map(a => {
                this._log.log(`脚本就绪 [id:${id}]: ${script}`);
                this._setAppAisle(a);
                return a;
            }));
    }

    private _setAppAisle(appAisle: AppAisle) {
        this._appAisle = appAisle;
        this._sub();
        this._ready = true;
        this.triggerEvent('start', null);
    }

    get aisle() {
        return this._appAisle;
    }

    get id() {
        return this._appAisle ? this._appAisle.id : null;
    }

    send: AppAisle<Application.CmdData_Client>['cmd'] = (cmd, data) => {
        if (!this.aisle) throw this._err('未初始化');
        if (!this.aisle.isConnected) throw this._err('连接失败');
        return this.aisle.cmd(cmd, data);
    }

    start() {
        return this._runScript()
            .pipe(map(() => null));
    }

    private _destroy() {
        if (!this.id) return of(null);
        return from(this.runTime.operatorManager.find(this.id))
            .pipe(mergeMap(o => this.runTime.operatorManager.destroy(o.appid, o.id)))
            .pipe(last(null, null))
            .pipe(map(() => null));

    }

    close() {
        return this.triggerEventEnd('close', { preventClose: false })
            .pipe(map(data => data.preventClose));
    }

    destroy() {
        if (!this._appAisle) {
            this.triggerEvent('destroy', null);
            return of(false);
        }
        return this._appAisle.close();
    }

    message(data: Application.Message) {
        return this.send('app_event_message', data);
    }

}